package cn.com.starbucks.basic.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Description:  controller层log全局处理
 *
 * @author tecsmile@outlook.com
 * @version 1.0
 * @date 2021/4/16 9:56 上午
 **/

@Slf4j
@Aspect
@Component
public class LogAspect {

  public static final String BASE_PATH = "basePath";
  private ObjectMapper objectMapper;

  @Autowired
  public void setObjectMapper(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
  }

  @Pointcut("within(cn.com.starbucks.reservation..*Controller)")
  public void actionLog() {
    // do nothing
  }

  @Around("actionLog()")
  public Object doActionAround(ProceedingJoinPoint joinPoint) throws Throwable {
    LocalDateTime startTime = LocalDateTime.now();
    Map<String, Object> logMap = new HashMap<>(1 << 2);
    // 获取当前请求对象
    ServletRequestAttributes attributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if (Objects.nonNull(attributes)) {
      HttpServletRequest request = attributes.getRequest();
      String urlStr = request.getRequestURL().toString();
      Signature signature = joinPoint.getSignature();
      MethodSignature methodSignature = (MethodSignature) signature;
      logMap.put("methodName", methodSignature.getMethod().getName());
      logMap.put("parameter", translateParameter(joinPoint));
      logMap.put(BASE_PATH, urlStr);
      logMap.put("requestId", request.getHeader("requestId"));
      logMap.put("ip", request.getRemoteHost());
      logMap.put("method", request.getMethod());
      logMap.put("startTime", startTime);
      logMap.put("uri", request.getRequestURI());
    }
    Object result;
    try {
      result = joinPoint.proceed();
      LocalDateTime endTime = LocalDateTime.now();
      Duration spendTime = Duration.between(startTime, endTime);
      logMap.put("result", result);
      logMap.put("spendTime", spendTime.toMillis());
      String logStr = objectMapper.writeValueAsString(logMap);
      log.info(logStr);
      return result;
    } catch (Exception e) {
      log.error("LogAspect LogAspect ERROR:{}", e.getMessage());
      LocalDateTime endTime = LocalDateTime.now();
      Duration spendTime = Duration.between(startTime, endTime);
      logMap.put("spendTime", spendTime.toMillis());
      logMap.put("error", e.getMessage());
      String logStr = objectMapper.writeValueAsString(logMap);
      log.info(logStr);
      throw e;
    }
  }

  /**
   * translate annotations parameter to map
   *
   * @param joinPoint joinPoint
   * @return Map<String, Object> results
   */
  private Map<String, Object> translateParameter(ProceedingJoinPoint joinPoint) {
    Signature signature = joinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();
    Object[] args = joinPoint.getArgs();
    Parameter[] parameters = method.getParameters();
    List<Object> requestBodyArgList = new ArrayList<>();
    Map<String, Object> requestParamArg = new HashMap<>(parameters.length);
    Map<String, Object> modelAttributeArg = new HashMap<>(parameters.length);
    Map<String, Object> pathVariableArg = new HashMap<>(parameters.length);
    Map<String, Object> paramArg = new HashMap<>(parameters.length);
    Map<String, Object> argMap = new HashMap<>(parameters.length);
    try {
      //解析不同类型请求参数
      for (int i = 0; i < parameters.length; i++) {
        RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
        if (requestBody != null) {
          requestBodyArgList.add(args[i]);
        }
        RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
        if (requestParam != null) {
          String key = parameters[i].getName();
          if (!StringUtils.isEmpty(requestParam.value())) {
            key = requestParam.value();
          }
          requestParamArg.put(key, args[i]);
        }
        ModelAttribute modelAttribute = parameters[i].getAnnotation(ModelAttribute.class);
        if (modelAttribute != null) {
          String key = parameters[i].getName();
          if (!StringUtils.isEmpty(modelAttribute.value())) {
            key = modelAttribute.value();
          }
          modelAttributeArg.put(key, args[i]);
        }
        PathVariable pathVariable = parameters[i].getAnnotation(PathVariable.class);
        if (pathVariable != null) {
          String key = parameters[i].getName();
          if (!StringUtils.isEmpty(pathVariable.value())) {
            key = pathVariable.value();
          }
          pathVariableArg.put(key, args[i]);
        }
        if (Objects.isNull(requestBody)
            && Objects.isNull(requestParam)
            && Objects.isNull(modelAttribute)
            && Objects.isNull(pathVariable)
            && Objects.nonNull(args[i])) {
          paramArg.put(parameters[i].getName(), args[i]);
        }
      }
      argMap.put("RequestBody", requestBodyArgList);
      argMap.put("RequestParam", requestParamArg);
      argMap.put("ModelAttribute", modelAttributeArg);
      argMap.put("pathVariable", pathVariableArg);
      argMap.put("paramArg", paramArg);
    } catch (Exception e) {
      log.error("get param fail message: {}", e.getMessage());
    }
    return argMap;
  }

}
